#ifndef	__ASSEMBLER__
#define	__ASSEMBLER__
#endif
#include <inc/mmu.h>
#include <boot/boot.h>

	      //    kernel code segment selector
#define	PROT_MODE_CSEG 0x8
	      //    kernel data segment selector
#define	PROT_MODE_DSEG 0x10
	      //    protected mode enable flag
#define	CR0_PE_ON      0x1

	      .text
	      .globl main
main:

	      //    the following codes are copied from osdev wiki
	      .code16

	      //    detect the memory layout before swiching into protect mode.

	      xorw  %ax, %ax
	      movw  %ax, %es
	      movw  $0x1004, %di

	      //    ebx must be 0 to start
	      xor   %ebx, %ebx
	      //    keep an entry count in bp
	      xor   %ebp, %ebp
	      //    Place "SMAP" into edx

	      movl  $0x0534D4150, %edx
	      movl  $0xe820, %eax
	      //    force a valid ACPI 3.X entry
	      movl  $1, %es:20(%di)
	      //    ask for 24 bytes
	      movl  $24, %ecx
	      int   $0x15
	      //    carry set on first call means "unsupported function"
	      jc    mm_failed
	      //    Some BIOSes apparently trash this register?
	      movl  $0x0534D4150, %edx
	      //    on success, eax must have been reset to "SMAP"
	      cmp   %eax, %edx
	      jne   mm_failed
	      //    ebx = 0 implies list is only 1 entry long (worthless)
	      test  %ebx, %ebx
	      je    mm_failed
	      jmp   mm_jmpin
mm_e820lp:
	      //    eax, ecx get trashed on every int 0x15 call
	      movl  $0xe820, %eax
	      //    force a valid ACPI 3.X entry
	      movl  $1, %es:20(%di)
	      //    ask for 24 bytes again
	      movl  $24, %ecx
	      int   $0x15
	      //    carry set means "end of list already reached"
	      jc    mm_e820f
	      //    repair potentially trashed register
	      mov   $0x0534D4150, %edx
mm_jmpin:
	      //    skip any 0 length entries
	      jcxz  mm_skipent
	      //    got a 24 byte ACPI 3.X response?
	      cmp   $20, %cl
	      jbe   mm_notext
	      //    if so: is the "ignore this data" bit clear?
	      testb $1, %es:20(%di)
	      je    mm_skipent
mm_notext:
	      //    get lower dword of memory region length
	      movl  %es:8(%di), %ecx
	      //    is the qword == 0?
	      test  %ecx, %ecx
	      jne   mm_goodent
	      //    get upper dword of memory region length
	      movl  %es:12(%di), %ecx
	      //    if length qword is 0, skip entry
	      jecxz mm_skipent
mm_goodent:
	      //    got a good entry: ++count, move to next storage spot
	      inc   %ebp
	      add   $24, %di
mm_skipent:
	      //    if ebx resets to 0, list is complete
	      test  %ebx, %ebx
	      jne   mm_e820lp
mm_e820f:
	      //    store the entry count
	      movl  %ebp, %es:0x1000
	      //    test opcode cleared carry flag
	      jmp   mm_exit
mm_failed:
	      //    "function unsupported" error exit
	      movl  $0, %es:0x1000
mm_exit:

	      // here start to enter protected mode
	      .code16

	      cli
	      //    String operations increment
	      cld

	      //    Set up the important data segment registers (DS, ES, SS).
	      xorw  %ax,%ax             # Segment number zero
	      movw  %ax,%ds             # -> Data Segment
	      movw  %ax,%es             # -> Extra Segment
	      movw  %ax,%ss             # -> Stack Segment

	      //    Enable A20:
	      //    For backwards compatibility with the earliest PCs, physical
	      //    address line 20 is tied low, so that addresses higher than
	      //    1MB wrap around to zero by default.  This code undoes this.
seta20_1:
	      inb   $0x64,%al               // Wait for not busy
	      testb $0x2,%al
	      jnz   seta20_1

	      movb  $0xd1,%al               // 0xd1 -> port 0x64
	      outb  %al,$0x64

seta20_2:
	      inb   $0x64,%al               // Wait for not busy
	      testb $0x2,%al
	      jnz   seta20_2

	      movb  $0xdf,%al               // 0xdf -> port 0x60
	      outb  %al,$0x60

	      //    Switch from real to protected mode, using a bootstrap GDT
	      //    and segment translation that makes virtual addresses
	      //    identical to their physical addresses, so that the
	      //    effective memory map does not change during the switch.

	      lgdt  gdtdesc
	      movl  %cr0, %eax
	      orl   $CR0_PE_ON, %eax
	      movl  %eax, %cr0

	      //    Jump to next instruction, but in 32-bit code segment.
	      //    Switches processor into 32-bit mode.

	      ljmp  $PROT_MODE_CSEG, $protcseg

	      //    32-bit mode code.
	      .code32

protcseg:
	      //    Set up the protected-mode data segment registers
	      movw  $PROT_MODE_DSEG, %ax    // Our data segment selector
	      movw  %ax, %ds                // -> DS: Data Segment
	      movw  %ax, %es                // -> ES: Extra Segment
	      movw  %ax, %fs                // -> FS
	      movw  %ax, %gs                // -> GS
	      movw  %ax, %ss                // -> SS: Stack Segment

	      movl  $main, %esp
	      call  waitdisk

	      movw  $0x1f2, %dx
	      movb  $STAGE1_SECT_COUNT, %al
	      outb  %al, %dx
	      movl  $STAGE1_SECT_START, %eax

	      inc   %dx
	      outb  %al, %dx
	      shr   $8, %eax

	      inc   %dx
	      outb  %al, %dx
	      shr   $8, %eax

	      inc   %dx
	      outb  %al, %dx
	      shr   $8, %eax

	      orb   $0xe0, %al
	      inc   %dx
	      outb  %al, %dx

	      movb  $0x20, %al
	      inc   %dl
	      outb  %al, %dx

	      movw  $STAGE1_SECT_COUNT, %bx
	      movl  $STAGE1_START, %edi
readsect:
	      call  waitdisk

	      movw  $0x1f0, %dx
	      cld

	      movl  $(SECT_SIZE / 4), %ecx
	      repnz insl

	      dec   %bx
	      jnz   readsect

	      movl  $STAGE1_START, %eax
	      jmp   *%eax

	      //    Wait for disk ready
waitdisk:
	      movw  $0x1f7, %dx
waitdisk_1:
	      inb   %dx, %al
	      andb  $0xC0, %al
	      cmpb  $0x40, %al
	      jne   waitdisk_1
	      ret

	      //    Bootstrap GDT
	      .align 4                                // force 4 byte alignment
gdt:
	      SEG_NULL // null seg
	      SEG(STA_X|STA_R, 0x0, 0xffffffff)	        // code seg
	      SEG(STA_W, 0x0, 0xffffffff)	        // data seg

gdtdesc:
	      .word 0x17                            // sizeof(gdt) - 1
	      .long gdt                             // address gdt
